/**
 * \file  mutants.cpp
 * \brief Definition of Mutants implementation class.
 *
 * LAV: Lib Anti Virals
 * <http://lav.googlecode.com/>
 * Copyright (C) 2013 Boretto Martin, Lenarduzzi Federico,
 *                    Alberione Fernando, Ocampo Emanuel FuDePAN
 *
 * This file is part of the LAV project.
 *
 * Contents:       Cpp file for LAV providing class Mutants implementation.
 *
 * System:         LAV
 * Language:       C++
 *
 * Author:         Boretto Martin
 * E-Mail:         martinboretto@gmail.com
 *
 * Author:         Lenarduzzi Federico
 * E-Mail:         federicolenarduzzi@gmail.com
 *
 * Author:         Alberione Fernando
 * E-Mail:         falberione037@gmail.com
 *
 * Author:         Ocampo Emanuel
 * E-Mail:         eocampo86@gmail.com
 *
 * LAV is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LAV is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LAV.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "lav/antiviral_set.h"

namespace lav
{

void AntiviralSet::Mutants::generate_mutants(const biopp::PseudoNucSequence& sequence,
        const CombinationDistanceContainer& combinations,
        MutantContainer& mutants_generated) const
{
    biopp::PseudoNucSequence current_sequence_mutant;
    CombinationDistanceContainer::const_iterator it_combinations;
    for (it_combinations = combinations.begin(); it_combinations != combinations.end(); ++it_combinations)
    {
        // Processing combinations to generate a new mutant according to this one
        current_sequence_mutant = sequence;
        AminoacidExpansion::const_iterator it_triplets;
        AminoacidExpansion current_triplet = it_combinations->triplets_combinations;
        for (it_triplets = current_triplet.begin(); it_triplets != current_triplet.end(); ++it_triplets)
            // Each pair (pos, triplet) is changed in the current_sequence_mutant
            current_sequence_mutant.set_triplet(it_triplets->pos, it_triplets->triplet);

        Mutant current_mutant(*it_combinations, current_sequence_mutant);
        // The current mutant is ready to be inserted
        mili::insert_into(mutants_generated, current_mutant);
    }
}


void AntiviralSet::Mutants::join_antivirals(PossibleMutants::PosAminoacidsTripletsContainer& join_antivirals) const
{
    ArvsSet::const_iterator it_av;
    for (it_av = antivirals.begin(); it_av != antivirals.end(); ++it_av)
        get_arv_information(it_av, join_antivirals);
}


void AntiviralSet::Mutants::get_applicable(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
        const PossibleMutants& possible_mutants, const PossibleMutants::Id id,
        AntiviralSet& applicable_antivirals) const
{
    ArvsSet::const_iterator it_av;
    PossibleMutants::MutantSeq mutant;
    possible_mutants.get_mutant(id, mutant);
    PossibleMutants::TripletPositionContainer triplet_position_container;
    possible_mutants.mutant_to_triplets(join_all_antivirals, mutant, triplet_position_container);
    for (it_av = antivirals.begin(); it_av != antivirals.end(); ++it_av)
    {
        if (((*it_av)->min_dist_to_antiviral(triplet_position_container)) > ZERO_DISTANCE)
            mili::insert_into(applicable_antivirals.antivirals, *it_av);
    }
}


void AntiviralSet::Mutants::get_applicable_populations(const PossibleMutants::PopulationsIds& ids, AntiviralSet& applicable_antivirals) const
{
    assert(ids.size() >= 1);
    PossibleMutants::PosAminoacidsTripletsContainer join_all_antivirals;
    join_antivirals(join_all_antivirals);
    PossibleMutants possible_mutants(join_all_antivirals);
    AntiviralSet current_arvs_applicable;
    PossibleMutants::PopulationsIds::const_iterator it_id = ids.begin();
    get_applicable(join_all_antivirals, possible_mutants, *it_id, applicable_antivirals);
    ++it_id;
    for (; it_id != ids.end(); ++it_id)
    {
        get_applicable(join_all_antivirals, possible_mutants, *it_id, current_arvs_applicable);
        intersection_set(current_arvs_applicable, applicable_antivirals);
        current_arvs_applicable.antivirals.clear();
    }
}


PossibleMutants::Id AntiviralSet::Mutants::get_id_from_sequence(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
        const PossibleMutants& possible_mutants, const biopp::NucSequence& sequence) const
{
    biopp::Triplet triplet;
    PossibleMutants::MutantSeq mutant;
    PossibleMutants::MultibaseNumberDigit digit;
    PossibleMutants::Id id;
    mutant.resize(possible_mutants.get_mutantSize());
    for (size_t i = 0; i < possible_mutants.get_mutantSize(); ++i)
    {
        triplet = sequence.get_triplet(join_all_antivirals[i].pos_resistance);
        join_all_antivirals[i].get_digit_of_triplet(triplet, digit);
        mutant[i] = digit;
    }
    id = possible_mutants.get_id(mutant);
    return id;
}


void AntiviralSet::Mutants::get_sequence_from_id(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
        const PossibleMutants& possible_mutants, const PossibleMutants::Id id,
        biopp::NucSequence& sequence) const
{
    biopp::Triplet triplet;
    PossibleMutants::MutantSeq mutant;
    possible_mutants.get_mutant(id, mutant);
    for (size_t i = 0; i < possible_mutants.get_mutantSize(); ++i)
    {
        if (mutant[i] != INVALID_TRIPLET)
        {
            join_all_antivirals[i].get_triplet_of_digit(mutant[i], triplet);
            sequence.set_triplet(join_all_antivirals[i].pos_resistance, triplet);
        }
    }
}


void AntiviralSet::Mutants::get_mutants(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                                        const PossibleMutants& possible_mutants, const AntiviralSet& applicable_antivirals,
                                        const PossibleMutants::Id id, PossibleMutants::PopulationsIds& mutants) const
{
    PossibleMutants::MutantSeq mutant;
    possible_mutants.get_mutant(id, mutant);
    ARVExpansionContainer combination;
    ARVExpansionContainer::const_iterator it_arv_exp_container;
    applicable_antivirals.combinator.combine_antivirals(combination);
    for (it_arv_exp_container = combination.begin(); it_arv_exp_container != combination.end(); ++it_arv_exp_container)
    {
        combination_apply(it_arv_exp_container, join_all_antivirals, possible_mutants, mutant, mutants);
    }
}


void AntiviralSet::Mutants::get_mutants_populations(const PossibleMutants::PopulationsIds& ids, const AntiviralSet& applicable_antivirals,
        PossibleMutants::PopulationsIds& all_mutants) const
{
    PossibleMutants::PosAminoacidsTripletsContainer join_all_antivirals;
    join_antivirals(join_all_antivirals);
    PossibleMutants possible_mutants(join_all_antivirals);
    PossibleMutants::PopulationsIds::const_iterator it_id;
    for (it_id = ids.begin(); it_id != ids.end(); ++it_id)
    {
        get_mutants(join_all_antivirals, possible_mutants, applicable_antivirals, *it_id, all_mutants);
    }
}


void AntiviralSet::Mutants::get_mutants_catch_up(const PossibleMutants::Id id, PossibleMutants::PopulationsIds& mutants) const
{
    MutantsStack stack_ids;
    stack_ids.push_front(id);
    PossibleMutants::PosAminoacidsTripletsContainer join_all_antivirals;
    join_antivirals(join_all_antivirals);
    PossibleMutants possible_mutants(join_all_antivirals);
    PossibleMutants::Id current_id;
    while (!stack_ids.empty())
    {
        current_id = stack_ids.front();
        stack_ids.pop_front();
        if (!exist_id(current_id, mutants))
        {
            get_catch_up(join_all_antivirals, possible_mutants, current_id, stack_ids);
            mili::insert_into(mutants, current_id);
        }
    }
}

// -----------------------
// Private methods section
// -----------------------
void AntiviralSet::Mutants::get_arv_information(const ArvsSet::const_iterator& it_av,
        PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const
{
    ResisBasedAntiviral::ResistancesGroupContainer::const_iterator it_groups;
    ResisBasedAntiviral::ResistancesGroupContainer& current_group = (*it_av)->resGroups;
    for (it_groups = current_group.begin(); it_groups != current_group.end(); ++it_groups)
        get_group_information(it_groups, join_resistence);
}


void AntiviralSet::Mutants::get_group_information(const ResisBasedAntiviral::ResistancesGroupContainer::const_iterator& it_groups,
        PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const
{
    ResisBasedAntiviral::ResistancePositionContainer current_resistance = it_groups->resistances;
    ResisBasedAntiviral::ResistancePositionContainer::const_iterator it_resistances;
    for (it_resistances = current_resistance.begin(); it_resistances != current_resistance.end(); ++it_resistances)
        add_resistence_to_join(it_resistances, join_resistence);
}


void AntiviralSet::Mutants::add_resistence_to_join(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistances,
        PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const
{
    PossibleMutants::PosAminoacidsTripletsContainer::iterator it_join_resistence;
    it_join_resistence = join_resistence.begin();
    bool found = false;
    while ((it_join_resistence != join_resistence.end()) && (!found))
    {
        if (it_join_resistence->pos_resistance == it_resistances->pos)
        {
            get_aminoacid_triplets(it_resistances->aminoacids, it_join_resistence->aminoacids_triplets);
            found = true;
        }
        ++it_join_resistence;
    }
    if (!found)
    {
        PossibleMutants::PosAminoacidsTriplets pos_amin_triplets(it_resistances->pos);
        get_aminoacid_triplets(it_resistances->aminoacids, pos_amin_triplets.aminoacids_triplets);
        mili::insert_into(join_resistence, pos_amin_triplets);
    }
}


void AntiviralSet::Mutants::get_aminoacid_triplets(const std::string& aminoacids,
        PossibleMutants::AminoacidTripletsContainer& aminoacids_triplets) const
{
    for (size_t i = 0; i < aminoacids.size(); ++i)
    {
        if (!exist_amin(aminoacids[i], aminoacids_triplets))
        {
            PossibleMutants::AminoacidTriplets new_amin_triplets(aminoacids[i]);
            biopp::TripletsIterator it_triplets;
            for (it_triplets = biopp::GeneticCode::triplets(aminoacids[i]); !it_triplets.end(); ++it_triplets)
            {
                mili::insert_into(new_amin_triplets.triplets, *it_triplets);
            }
            mili::insert_into(aminoacids_triplets, new_amin_triplets);
        }
    }
}


bool AntiviralSet::Mutants::exist_amin(char amin, PossibleMutants::AminoacidTripletsContainer& amin_triplets) const
{
    bool found = false;
    PossibleMutants::AminoacidTripletsContainer::const_iterator it_amin_triplets = amin_triplets.begin();
    while ((it_amin_triplets != amin_triplets.end()) && (!found))
    {
        if (it_amin_triplets->aminoacid[0] == amin)
            found = true;
        ++it_amin_triplets;
    }
    return found;
}


void AntiviralSet::Mutants::combination_apply(const ARVExpansionContainer::const_iterator& it_arv_exp_container,
        const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
        const PossibleMutants& possible_mutants, const PossibleMutants::MutantSeq& mutant,
        PossibleMutants::PopulationsIds& mutants) const
{
    ARVExpansion::const_iterator it_arv_exp;
    AminoacidExpansion::const_iterator it_exp_resis;
    AminoacidExpansion current_exp_resis;
    PossibleMutants::MutantSeq current_mutant;
    for (it_arv_exp = (*it_arv_exp_container).begin(); it_arv_exp != (*it_arv_exp_container).end(); ++it_arv_exp)
    {
        current_exp_resis = (*it_arv_exp);
        current_mutant = mutant;
        for (it_exp_resis = current_exp_resis.begin(); it_exp_resis != current_exp_resis.end(); ++it_exp_resis)
        {
            resistance_apply(it_exp_resis, join_all_antivirals, current_mutant);
        }
        mutants.push_front(possible_mutants.get_id(current_mutant));
    }

}

void AntiviralSet::Mutants::resistance_apply(const AminoacidExpansion::const_iterator& it_exp_resis,
        const PossibleMutants::PosAminoacidsTripletsContainer& join_antivirals,
        PossibleMutants::MutantSeq& current_mutant) const
{
    size_t i = 0;
    PossibleMutants::MultibaseNumberDigit digit;
    while (it_exp_resis->pos != join_antivirals[i].pos_resistance)
    {
        ++i;
    }
    join_antivirals[i].get_digit_of_triplet(it_exp_resis->triplet, digit);
    current_mutant[i] = digit;
}


bool AntiviralSet::Mutants::exist_id(const PossibleMutants::Id id, const PossibleMutants::PopulationsIds& mutants) const
{
    bool found = false;
    PossibleMutants::PopulationsIds::const_iterator it_ids = mutants.begin();
    while ((it_ids != mutants.end()) && (!found))
    {
        if (id == *it_ids)
        {
            found = true;
        }
        ++it_ids;
    }
    return found;
}


void AntiviralSet::Mutants::intersection_set(const AntiviralSet& current_arvs_applies, AntiviralSet& antivirals_applies) const
{
    ArvsSet::const_iterator it_current_arvs;
    ArvsSet::iterator it_arvs_ap;
    for (it_arvs_ap = antivirals_applies.begin(); it_arvs_ap != antivirals_applies.end(); ++it_arvs_ap)
    {
        it_current_arvs = current_arvs_applies.begin();
        while ((it_current_arvs != current_arvs_applies.end()) && (*it_current_arvs != *it_arvs_ap))
        {
            ++it_current_arvs;
        }
        if (*it_current_arvs != *it_arvs_ap)
        {
            antivirals_applies.remove(*it_arvs_ap);
        }
    }
}

void AntiviralSet::Mutants::get_catch_up(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
        const PossibleMutants& possible_mutants, const PossibleMutants::Id current_id,
        MutantsStack& stack_ids) const
{
    AntiviralSet current_applicable_arvs;
    get_applicable(join_all_antivirals, possible_mutants, current_id, current_applicable_arvs);
    if (current_applicable_arvs.size() > 0)
    {
        get_mutants(join_all_antivirals, possible_mutants, current_applicable_arvs, current_id, stack_ids);
        current_applicable_arvs.antivirals.clear();
    }
}
}